/******************************************************************************
 *
 * Copyright (C) 2011 - 2014 Xilinx, Inc.  All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * Use of the Software is limited solely to applications:
 * (a) running on a Xilinx device, or
 * (b) that interact with a Xilinx device through a bus or interconnect.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of the Xilinx shall not be used
 * in advertising or otherwise to promote the sale, use or other dealings in
 * this Software without prior written authorization from Xilinx.
 *
 ******************************************************************************/
/***************************** Include Files *********************************/

#include "xparameters.h"	/* EDK generated parameters */
#include "xspi.h"		/* SPI device driver */
#include "xil_printf.h"

#include "qspi/qspi.h"
#define SPI_DEVICE_ID			XPAR_SPI_1_DEVICE_ID

#define SPI_SELECT 			0x01




/**
 * This definitions specify the EXTRA bytes in each of the command
 * transactions. This count includes Command byte, address bytes and any
 * don't care bytes needed.
 */
#define READ_WRITE_EXTRA_BYTES		4 /* Read/Write extra bytes */
#define	WRITE_ENABLE_BYTES		1 /* Write Enable bytes */
#define SECTOR_ERASE_BYTES		4 /* Sector erase extra bytes */
#define BULK_ERASE_BYTES		1 /* Bulk erase extra bytes */
#define STATUS_READ_BYTES		2 /* Status read bytes count */
#define STATUS_WRITE_BYTES		2 /* Status write bytes count */

/*
 * Flash not busy mask in the status register of the flash device.
 */
#define FLASH_SR_IS_READY_MASK		0x01 /* Ready mask */

/*
 * Number of bytes per page in the flash device.
 */
#define PAGE_SIZE			256

/*
 * The following definitions specify the number of dummy bytes to ignore in the
 * data read from the flash, through various Read commands. This is apart from
 * the dummy bytes returned in reponse to the command and address transmitted.
 */
/*
 * After transmitting Dual Read command and address on DIO0, the quad spi device
 * configures DIO0 and DIO1 in input mode and receives data on both DIO0 and
 * DIO1 for 8 dummy clock cycles. So we end up with 16 dummy bits in DRR. The
 * same logic applies Quad read command, so we end up with 4 dummy bytes in that
 * case.
 */
#define DUAL_READ_DUMMY_BYTES		2
#define QUAD_READ_DUMMY_BYTES		4

#define DUAL_IO_READ_DUMMY_BYTES	1
#define QUAD_IO_READ_DUMMY_BYTES	3

namespace SPIFlash {
	XSpi Spi;	// todo make private

	namespace {


		/*
		 * Buffers used during read and write transactions.
		 */
		u8 ReadBuffer[PAGE_SIZE + READ_WRITE_EXTRA_BYTES + 4];
		u8 WriteBuffer[PAGE_SIZE + READ_WRITE_EXTRA_BYTES];

		/*****************************************************************************/
		/**
		 *
		 * This function waits till the Spansion serial Flash is ready to accept next
		 * command.
		 *
		 * @param	None
		 *
		 * @return	XST_SUCCESS if successful else XST_FAILURE.
		 *
		 * @note		This function reads the status register of the Buffer and waits
		 *.		till the WIP bit of the status register becomes 0.
		 *
		 ******************************************************************************/
		int waitForFlashReady(void) {
			int Status;
			u8 StatusReg;

			while (1) {

				/*
				 * Get the Status Register.
				 */
				Status = getStatus(&Spi);
				if (Status != XST_SUCCESS) {
					return XST_FAILURE;
				}

				/*
				 * Check if the flash is ready to accept the next command.
				 * If so break.
				 */
				StatusReg = ReadBuffer[1];
				if ((StatusReg & FLASH_SR_IS_READY_MASK) == 0) {
					break;
				}
			}

			return XST_SUCCESS;
		}
	}

	int init() {
		int Status;
		XSpi_Config *ConfigPtr; /* Pointer to Configuration data */

		/*
		 * Initialize the SPI driver so that it's ready to use,
		 * specify the device ID that is generated in xparameters.h.
		 */
		ConfigPtr = XSpi_LookupConfig(SPI_DEVICE_ID);
		if (ConfigPtr == NULL) {
			return XST_DEVICE_NOT_FOUND;
		}

		Status = XSpi_CfgInitialize(&Spi, ConfigPtr, ConfigPtr->BaseAddress);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Set the SPI device as a master and in manual slave select mode such
		 * that the slave select signal does not toggle for every byte of a
		 * transfer, this must be done before the slave select is set.
		 */
		Status = XSpi_SetOptions(&Spi, XSP_MASTER_OPTION |
		XSP_MANUAL_SSELECT_OPTION);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Select the quad flash device on the SPI bus, so that it can be
		 * read and written using the SPI bus.
		 */
		Status = XSpi_SetSlaveSelect(&Spi, SPI_SELECT);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Start the SPI driver so that interrupts and the device are enabled.
		 */
		XSpi_Start(&Spi);
		XSpi_IntrGlobalDisable(&Spi);	// use polling-mode

		Status = quadEnable(&Spi);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*
	 * Byte offset value written to Flash. This needs to be redefined for writing
	 * different patterns of data to the Flash device.
	 */
	//static u8 TestByte = 0x20;
	#if 0
		int qspi_test(void)
		{

			/*
			 * Perform the Write Enable operation.
			 */
			Status = SpiFlashWriteEnable(&Spi);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Perform the Sector Erase operation.
			 */
			Status = SpiFlashSectorErase(&Spi, Address);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Set the Quad Enable (QE) bit in the flash device, so that Quad
			 * operations can be perfomed on the flash.
			 */
			Status = SpiFlashQuadEnable(&Spi);
			if (Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Perform the Write Enable operation.
			 */
			Status = SpiFlashWriteEnable(&Spi);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Write the data to the Page using Page Program command.
			 */
			Status = SpiFlashWrite(&Spi, Address, PAGE_SIZE, COMMAND_PAGE_PROGRAM);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Clear the read Buffer.
			 */
			for(Index = 0; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES; Index++) {
				ReadBuffer[Index] = 0x0;
			}

			/*
			 * Read the data from the Page using Random Read command.
			 */
			Status = SpiFlashRead(&Spi, Address, PAGE_SIZE, COMMAND_RANDOM_READ);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Compare the data read against the data written.
			 */
			for(Index = 0; Index < PAGE_SIZE; Index++) {
				if(ReadBuffer[Index + READ_WRITE_EXTRA_BYTES] !=
							(u8)(Index + TestByte)) {
					return XST_FAILURE;
				}
			}

			/*
			 * Clear the Read Buffer.
			 */
			for(Index = 0; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES +
				DUAL_READ_DUMMY_BYTES; Index++) {
				ReadBuffer[Index] = 0x0;
			}

			/*
			 * Read the data from the Page using Dual Output Fast Read command.
			 */
			Status = SpiFlashRead(&Spi, Address, PAGE_SIZE, COMMAND_DUAL_READ);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Compare the data read against the data written.
			 */
			for(Index = 0; Index < PAGE_SIZE; Index++) {
				if(ReadBuffer[Index + READ_WRITE_EXTRA_BYTES +
						DUAL_READ_DUMMY_BYTES] !=
							(u8)(Index + TestByte)) {
					return XST_FAILURE;
				}
			}

			/*
			 * Perform the Write Enable operation.
			 */
			Status = SpiFlashWriteEnable(&Spi);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Write the data to the next Page using Quad Fast Write command.
			 */
			TestByte = 0x09;
			Address += PAGE_SIZE;
			Status = SpiFlashWrite(&Spi, Address, PAGE_SIZE, COMMAND_QUAD_WRITE);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Wait while the Flash is busy.
			 */
			Status = SpiFlashWaitForFlashReady();
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Clear the read Buffer.
			 */
			for(Index = 0; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES;
				Index++) {
				ReadBuffer[Index] = 0x0;
			}

			/*
			 * Read the data from the Page using Normal Read command.
			 */
			Status = SpiFlashRead(&Spi, Address, PAGE_SIZE, COMMAND_RANDOM_READ);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Compare the data read against the data written.
			 */
			for(Index = 0; Index < PAGE_SIZE; Index++) {
				if(ReadBuffer[Index + READ_WRITE_EXTRA_BYTES] !=
							(u8)(Index + TestByte)) {
					return XST_FAILURE;
				}
			}

			/*
			 * Clear the read Buffer.
			 */
			for(Index = 0; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES +
				QUAD_READ_DUMMY_BYTES; Index++) {
				ReadBuffer[Index] = 0x0;
			}

			/*
			 * Read the data from the Page using Quad Output Fast Read command.
			 */
			Status = SpiFlashRead(&Spi, Address, PAGE_SIZE, COMMAND_QUAD_READ);
			if(Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			/*
			 * Compare the data read against the data written.
			 */
			for(Index = 0; Index < PAGE_SIZE; Index++) {
				if(ReadBuffer[Index + READ_WRITE_EXTRA_BYTES +
						QUAD_READ_DUMMY_BYTES] !=
							(u8)(Index + TestByte)) {
					return XST_FAILURE;
				}
			}

			return XST_SUCCESS;
		}
	#endif
	/*****************************************************************************/
	/**
	 *
	 * This function enables writes to the Spansion Serial Flash memory.
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		None
	 *
	 ******************************************************************************/
	int writeEnable(XSpi *SpiPtr) {
		int Status;

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Prepare the WriteBuffer.
		 */
		WriteBuffer[0] = (u8) COMMAND_WRITE_ENABLE;

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, NULL,
		WRITE_ENABLE_BYTES);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function writes the data to the specified locations in the Spansion Serial
	 * Flash memory.
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 * @param	Addr is the address in the Buffer, where to write the data.
	 * @param	ByteCount is the number of bytes to be written.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		None
	 *
	 ******************************************************************************/
	int writePage(XSpi *SpiPtr, u32 pageAddr, u8 *data, u8 WriteCmd) {
		u32 Index;
		int Status;

		u32 Addr = pageAddr * PAGE_SIZE;

		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		WriteBuffer[0] = WriteCmd;
		WriteBuffer[1] = (u8) (Addr >> 16);
		WriteBuffer[2] = (u8) (Addr >> 8);
		WriteBuffer[3] = (u8) Addr;

		for (Index = 4; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES; Index++) {
			WriteBuffer[Index] = data[Index-4];
		}

		Status = XSpi_Transfer(SpiPtr, WriteBuffer, NULL,
				(PAGE_SIZE + READ_WRITE_EXTRA_BYTES));
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function reads the data from the Spansion Serial Flash Memory
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 * @param	Addr is the starting address in the Flash Memory from which the
	 *		data is to be read.
	 * @param	ByteCount is the number of bytes to be read.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		None
	 *
	 ******************************************************************************/
	int readPage(XSpi *SpiPtr, u32 pageAddr, u8 *data, u8 ReadCmd) {
		int Status;
		u32 Addr = pageAddr * PAGE_SIZE;

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Prepare the WriteBuffer.
		 */
		WriteBuffer[0] = ReadCmd;
		WriteBuffer[1] = (u8) (Addr >> 16);
		WriteBuffer[2] = (u8) (Addr >> 8);
		WriteBuffer[3] = (u8) Addr;

		u32 ByteCount = PAGE_SIZE;

		uint32_t extra = 0;
		if (ReadCmd == (u8) COMMAND_DUAL_READ) {
			extra += 2;
		} else if (ReadCmd == (u8) COMMAND_DUAL_IO_READ) {
			extra++;
		} else if (ReadCmd == (u8) COMMAND_QUAD_IO_READ) {
			extra += 3;
		} else if (ReadCmd == (u8) COMMAND_QUAD_READ) {
			extra += 4;
		}
		ByteCount += extra;
		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, ReadBuffer,
				(ByteCount + READ_WRITE_EXTRA_BYTES));

		if (data) {
			for (int i = 0; i < PAGE_SIZE; i++) {
				data[i] = ReadBuffer[i + READ_WRITE_EXTRA_BYTES + extra];
			}
		}

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function erases the entire contents of the Spansion Serial Flash device.
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		The erased bytes will read as 0xFF.
	 *
	 ******************************************************************************/
	int bulkErase(XSpi *SpiPtr) {
		int Status;

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Prepare the WriteBuffer.
		 */
		WriteBuffer[0] = (u8) COMMAND_BULK_ERASE;

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, NULL,
		BULK_ERASE_BYTES);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function erases the contents of the specified Sector in the Spansion
	 * Serial Flash device.
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 * @param	Addr is the address within a sector of the Buffer, which is to
	 *		be erased.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		The erased bytes will be read back as 0xFF.
	 *
	 ******************************************************************************/
	int sectorErase(XSpi *SpiPtr, u32 pageNr) {
		int Status;

		u32 Addr = pageNr * PAGE_SIZE;

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Prepare the WriteBuffer.
		 */
		WriteBuffer[0] = (u8) COMMAND_SECTOR_ERASE;
		WriteBuffer[1] = (u8) (Addr >> 16);
		WriteBuffer[2] = (u8) (Addr >> 8);
		WriteBuffer[3] = (u8) (Addr);

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, NULL,
		SECTOR_ERASE_BYTES);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function reads the Status register of the Spansion Flash.
	 *
	 * @param	SpiPtr is a pointer to the instance of the Spi device.
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		The status register content is stored at the second byte pointed
	 *		by the ReadBuffer.
	 *
	 ******************************************************************************/
	int getStatus(XSpi *SpiPtr) {
		int Status;

		/*
		 * Prepare the Write Buffer.
		 */
		WriteBuffer[0] = (u8) COMMAND_STATUSREG_READ;

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, ReadBuffer,
		STATUS_READ_BYTES);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}

	/*****************************************************************************/
	/**
	 *
	 * This function sets the QuadEnable bit in Spansion flash.
	 *
	 * @param	None
	 *
	 * @return	XST_SUCCESS if successful else XST_FAILURE.
	 *
	 * @note		None.
	 *
	 ******************************************************************************/
	int quadEnable(XSpi *SpiPtr) {
		int Status;

		/*
		 * Perform the Write Enable operation.
		 */
		Status = writeEnable(SpiPtr);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Prepare the WriteBuffer.
		 */
		WriteBuffer[0] = 0x01;
		WriteBuffer[1] = 0x00;
		WriteBuffer[2] = 0x02; /* QE = 1 */

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, NULL, 3);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Wait while the Flash is busy.
		 */
		Status = waitForFlashReady();
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Verify that QE bit is set by reading status register 2.
		 */

		/*
		 * Prepare the Write Buffer.
		 */
		WriteBuffer[0] = 0x35;

		/*
		 * Initiate the Transfer.
		 */
		Status = XSpi_Transfer(SpiPtr, WriteBuffer, ReadBuffer,
		STATUS_READ_BYTES);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		return XST_SUCCESS;
	}
#if 0
	int eraseWriteVerifyQuad(int pageNr, u8 *data) {
		int Status;
		/*
		 * Perform the Write Enable operation.
		 */
		Status = writeEnable(&Spi);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Perform the Sector Erase operation.
		 */
		Status = sectorErase(&Spi, pageNr);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Set the Quad Enable (QE) bit in the flash device, so that Quad
		 * operations can be perfomed on the flash.
		 */
		Status = quadEnable(&Spi);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Perform the Write Enable operation.
		 */
		Status = writeEnable(&Spi);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Write the data to the Page using Page Program command.
		 */
		Status = writePage(&Spi, pageNr, data, (u8) COMMAND_PAGE_PROGRAM);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Clear the read Buffer.
		 */
		for (int Index = 0; Index < PAGE_SIZE + READ_WRITE_EXTRA_BYTES; Index++) {
			ReadBuffer[Index] = 0x0;
		}

		/*
		 * Read the data from the Page using Random Read command.
		 */
		Status = readPage(&Spi, pageNr, (u8*) 0, (u8) COMMAND_RANDOM_READ);
		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		/*
		 * Compare the data read against the data written.
		 */
		for (int Index = 0; Index < PAGE_SIZE; Index++) {
			if (ReadBuffer[Index + READ_WRITE_EXTRA_BYTES]
					!= data[Index]) {
				return XST_FAILURE;
			}
		}

		return XST_SUCCESS;
	}
#endif
}

